package com.gitee.l0km.aocache.config;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;

import com.gitee.l0km.aocache.AocacheLogger;
import com.google.common.base.CharMatcher;

/**
 * 分布式切入点(JoinPoint)缓存配置加载器
 * 
 * <h3>核心功能</h3>
 * <ul>
 *   <li>📁 多环境配置加载：支持从以下位置按优先级加载配置(后加载的配置会覆盖先加载的):
 *     <ol>
 *       <li>类路径下的META-INF/aocache.joinpoints配置文件</li>
 *       <li>${java.home}/.aocache/aocache.joinpoints文件</li>
 *       <li>环境变量AOCACHE_JOINPOINTS指定的文件</li>
 *     </ol>
 *   </li>
 *   <li>🔗 资源聚合机制：自动合并所有JAR包中同名配置文件的内容</li>
 *   <li>🔄 双重校验锁缓存：采用线程安全的延迟初始化方案保证配置单例，避免多线程环境下重复初始化</li>
 *   <li>⚡ 性能优化：配置加载后生成不可变Map避免并发修改问题</li>
 * </ul>
 *
 * <h3>配置规则</h3>
 * <ol>
 *   <li>配置文件格式为key=value</li>
 *   <li>同名配置项遵循"后来者优先"原则</li>
 *   <li>键值对前后空格自动修剪</li>
 * </ol>
 *
 * @author guyadong
 */
public class JoinPointConfigLoader {
    private static final String JOINPOINTS_FILE_NAME = "aocache.joinpoints";
    private static final String RESOURCE_PATH = "META-INF/" + JOINPOINTS_FILE_NAME;
    private static final CharMatcher SPACE_MATCHER = CharMatcher.is(' ');

    /**
     * 静态内部类Holder模式实现懒加载
     * <p>优势：
     * <ul>
     *   <li>🛡️ 线程安全：由JVM类加载机制保证</li>
     *   <li>⚡ 高效读取：无锁开销</li>
     *   <li>🔒 不可变性：使用Collections.unmodifiableMap包装</li>
     * </ul>
     */
    private static class Holder {
        private static final Map<String, Object> CONFIG_MAP = initConfigMap();

        private static Map<String, Object> initConfigMap() {
            Map<String, Object> tempMap = new LinkedHashMap<>();
            try {
                // 加载META-INF下的配置
                ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                Enumeration<URL> resources = classLoader.getResources(RESOURCE_PATH);
                
                while (resources.hasMoreElements()) {
                    URL url = resources.nextElement();
                    loadFromUrl(url, tempMap);
                }
                
                // 加载${java.home}/.aocache/aocache.joinpoints配置
                String javaHome = System.getProperty("java.home");
                Path configPath = Paths.get(javaHome, ".aocache", JOINPOINTS_FILE_NAME);
                if (Files.isRegularFile(configPath) && Files.isReadable(configPath)) {
                    loadFromUrl(configPath.toUri().toURL(), tempMap);
                }
                
                // 加载环境变量AOCACHE_JOINPOINTS指定的配置文件
                String envConfigPath = System.getenv("AOCACHE_JOINPOINTS");
                if (envConfigPath != null && !envConfigPath.isEmpty()) {
                    Path envPath = Paths.get(envConfigPath);
                    if (Files.isRegularFile(envPath) && Files.isReadable(envPath)) {
                        loadFromUrl(envPath.toUri().toURL(), tempMap);
                    }
                }
                
                // 调试输出配置信息
                if (AoCacheGlobalConfig.debugOutput) {
                    AocacheLogger.DEFAULT.log("Loaded aocache joinpoint configurations:");
                    for (Map.Entry<String, Object> entry : tempMap.entrySet()) {
                        AocacheLogger.DEFAULT.log("{}={}", entry.getKey(), entry.getValue());
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException("Failed to load joinpoint configurations", e);
            }
            return Collections.unmodifiableMap(tempMap);
        }
    }

    /**
     * 获取指定切入点(JoinPoint)的缓存配置
     * <p>功能特性：
     * <ul>
     *   <li>🔍 自动修剪空格：输入的切入点(JoinPoint)名称会自动去除所有空格</li>
     *   <li>⚡ 线程安全：由JVM类加载机制保证Holder类的单例初始化</li>
     *   <li>📦 空值防护：输入null或空时直接返回null</li>
     * </ul>
     *
     * <p><b>线程安全性</b>：
     * <ul>
     *   <li>🛡️ 线程安全：由JVM类加载机制保证Holder类的单例初始化</li>
     *   <li>🔒 不可变性：返回的配置映射表是不可修改的</li>
     * </ul>
     *
     * <p><b>参数格式要求</b>：
     * <ul>
     *   <li>格式：类全名.方法名</li>
     *   <li>示例：execution(public static int you.package.YouClass.methodA(java.lang.String))</li>
     *   <li>⚠️ 注意：方法名后必须包含完整的参数类型列表</li>
     * </ul>
     *
     * <p><b>返回值说明</b>：
     * <ul>
     *   <li>可能返回null：当参数为null/空字符串，或配置中不存在对应键时</li>
     *   <li>类型：返回CacheConfig.parseConfig解析后的对象（false或Map）</li>
     * </ul>
     *
     * <p><b>空格处理机制</b>：
     * <ul>
     *   <li>自动去除参数中的所有空格</li>
     *   <li>配置键名在加载时已去除所有空格</li>
     * </ul>
     *
     * @param joinPoint 切入点(JoinPoint)名称
     * @return 对应的缓存配置对象（可能为null）
     */
    public static Object joinPointConfigOf(String joinPoint) {
        return null == joinPoint || joinPoint.isEmpty() ? null : Holder.CONFIG_MAP.get(SPACE_MATCHER.removeFrom(joinPoint));
	}

	/**
     * 从指定URL加载属性文件并合并到目标配置映射表
     * <p><b>核心功能</b>：
     * <ul>
     *   <li>📖 读取配置：使用UTF-8编码读取输入流</li>
     *   <li>✂️ 格式处理：自动修剪键值的所有空格</li>
     *   <li>🔄 合并策略：后加载的配置项覆盖先加载的</li>
     *   <li>🔒 线程安全：方法本身无状态，操作参数由调用方保证线程安全</li>
     * </ul>
     *
     * <p><b>处理流程</b>：
     * <ol>
     *   <li>打开URL输入流，使用BufferedReader按行读取</li>
     *   <li>跳过空行和注释行（以#开头）</li>
     *   <li>解析每行的键值对（以=分隔）</li>
     *   <li>调用CacheConfig.parseConfig解析值对象</li>
     *   <li>将有效配置项存入configMap，键名忽略空格</li>
     * </ol>
     *
     * <p><b>注意事项</b>：
     * <ul>
     *   <li>⚠️ 本方法不负责configMap的线程安全，调用方需保证</li>
     *   <li>⚠️ 输入流会在方法结束时自动关闭</li>
     *   <li>⚠️ 无效配置行（无=分隔符）会被静默跳过</li>
     * </ul>
     *
     * @param url 配置文件资源定位符，不可为null
     * @param configMap 目标配置映射表，不可为null
     * @throws IOException 当发生I/O错误时抛出，包括：
     *                    <ul>
     *                      <li>URL连接失败</li>
     *                      <li>流读取异常</li>
     *                    </ul>
     * @throws NullPointerException 当url或configMap为null时抛出
     * @see CacheConfig#parseConfig(String, String) 值对象解析逻辑
     */
	private static void loadFromUrl(URL url, Map<String, Object> configMap) throws IOException {
		try (BufferedReader reader = new BufferedReader(
				new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
			String line;
			while ((line = reader.readLine()) != null) {
				line = line.trim();
				if (line.isEmpty() || line.startsWith("#")) {
					continue;
				}

				int separatorIndex = line.indexOf('=');
				if (separatorIndex > 0) {
					String key = line.substring(0, separatorIndex).trim();
					String value = line.substring(separatorIndex + 1).trim();
					Object config = CacheConfig.parseConfig(key, value);
					if (config != null) {
                        /** 键名忽略空格 */
						configMap.put(SPACE_MATCHER.removeFrom(key), config);
					}
				}
			}
		}
	}
}